/*************************************************************
 *Name:           Christopher Alexander
 *Email address:  alexanc2@onid.oregonstate.edu
 *Class name:     CS362-400
 *Assignment:     Team 9 - Project System Test
 *Description:    The program will peform system testing on the
 *                dominion.c file.  Complete games of Dominion
 *                will be played, with assertions used to check
 *                the various functions that are called.  Different
 *                game AIs will be employed to attempt to model a
 *                variety of gameplay strategies and scenarios.
 *Notes:          This program accepts the following command line
 *                arguments (but does not require them to run):
 *                -g number of times to play dominion
 *                -p number of players (2-4)
 *                -s seed number
 *************************************************************/

#include "dominion.h"
#include "interface.h"
#include <stdio.h>
#include "rngs.h"
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>

#define SEED_MIN 0
#define SEED_MAX 1000
#define GAMES_MIN 1
#define GAMES_MAX 2000
#define PLAYERS_MIN 2
#define PLAYERS_MAX 4
#define NUM_CARD_TYPES 27
#define NUM_KCARD_TYPES 20
#define NUM_AIS 4

/*Function Prototypes*/
void parseArgv(int, char**);
int* setRandomGameInitializers(void);
int* setKingdomCards(void);
void assignAIs(int playerAI[MAX_PLAYERS]);
void playDominion(void);
int testIsGameOver(struct gameState *preState);
void playACard(struct gameState *state);
void buyACard(struct gameState *state);


//Global variables.
int numGames = -1;
int numPlayers = -1;
int numSeed = -1;



/*Parse the command line looking for specified options
 *Params: argc, number of arguments.
 *        argv, array of arguments.
 *Return: None.
 */
void parseArgv(int argc, char **argv){
   char opt;

   /*Parse command line looking for option -n.*/
   while ((opt = getopt(argc, argv, "g:p:s:")) != -1){
      switch (opt){
         case 'g':
            /*optarg must be an int between SEED_MIN and SEED_MAX.*/
            numGames = strtol(optarg, NULL, 0);
            if (numGames < GAMES_MIN || numGames > GAMES_MAX){
               printf("Option -g requires an integer argument in the range "
                      "of %d-%d.\n", GAMES_MIN, GAMES_MAX);
               exit(EXIT_FAILURE);
            }
            break;

         case 'p':
            /*optarg must be an int between SEED_MIN and SEED_MAX.*/
            numPlayers = strtol(optarg, NULL, 0);
            if (numPlayers < PLAYERS_MIN || numPlayers > PLAYERS_MAX){
               printf("Option -p requires an integer argument in the range "
                      "of %d-%d.\n", PLAYERS_MIN, PLAYERS_MAX);
               exit(EXIT_FAILURE);
            }
            break;

         case 's':
            /*optarg must be an int between SEED_MIN and SEED_MAX.*/
            numSeed = strtol(optarg, NULL, 0);
            if (numSeed < SEED_MIN || numSeed > SEED_MAX){
               printf("Option -s requires an integer argument in the range "
                      "of %d-%d.\n", SEED_MIN, SEED_MAX);
               exit(EXIT_FAILURE);
            }
            break;
      }
   }
}


/*If command line args have not been used to set initial game conditions,
 *this function will randomly set those variables.
 *Params: None.
 *Return: a pointer to an array of randomly selected kingdom cards.
 */
int* setRandomGameInitializers() {
   int *kingCards;

   //Set the number of players to be between 2-4
   if (numPlayers < PLAYERS_MIN ) {
      numPlayers = (rand() % 3) + 2;
   }

   //Set the seed to use in initializeGame.
   if (numSeed < SEED_MIN) {
      numSeed = rand() % (SEED_MAX + 1);
   }

   kingCards = setKingdomCards();
   return kingCards;
}


/*Randomly choose 10 kingdom cards to play a game with.
 *Params: None.
 *Return: a pointer to an array of randomly selected kingdom cards.
 */
int* setKingdomCards() {
   int i;
   int usedCards[NUM_CARD_TYPES]; //Will keep track of cards that have been assigned.
   int* k = malloc(10 * sizeof(int));

   for(i = 0; i < 10; i++) {
      //Assign a kingdom card to this position in array.  The kingdom cards are in postions
      //7-26 in enum CARD (see dominion.h)
      k[i] = (rand() % NUM_KCARD_TYPES) + 7;
      //If the card is a duplicate, reassign until a unique card is chosen.
      while (usedCards[ (k[i]) ] == -1
             || k[i] == feast
             || k[i] == remodel
             || k[i] == treasure_map
             || k[i] == sea_hag
             || k[i] == ambassador
             || k[i] == steward
             || k[i] == tribute
             || k[i] == adventurer
             || k[i] == baron
             ) {
         k[i] = (rand() % NUM_KCARD_TYPES) + 7;
      }
      //This kingdom card has now been chosen and must not be chosen again.
      usedCards[ (k[i]) ] = -1;
   }
   return k;
}


/*Randomly assign AI to players.
 * AI 1 - Always plays a card, never buys a card.
 * AI 2 - Never plays a card, always buys a card.
 * AI 3 - Always plays a card, always buys a card.
 * AI 4 - Plays card 50% of time, buys card 50% of time.
 *Params: playerAI - will hold the assigned AI for each player
 *Return: None.
 */
void assignAIs(int playerAI[MAX_PLAYERS]){
   int i;

   for (i = 0; i < MAX_PLAYERS; i++){
      playerAI[i] = (rand() % NUM_AIS) + 5;
   }
}


/*This function is used to generate a value for gameOver that
 *should be identical to the value calculated by isGameOver()
 *Params: state - a struct gameState.
 *Return: 1 - if game is over.
 *        0 - if game is not over.
 */
int testIsGameOver(struct gameState *state) {

   int i;
   int numEmptyPiles;

   //if stack of Province cards is empty, the game ends
   if (state->supplyCount[province] == 0){
      return 1;
   }

   //if three supply pile are at 0, the game ends
   numEmptyPiles = 0;
   for (i = 0; i < NUM_CARD_TYPES; i++){
      if (state->supplyCount[i] == 0){
         numEmptyPiles++;
      }
   }
   if ( numEmptyPiles >= 3){
      return 1;
   }

   return 0;
}


/*If player will play a card on their turn, this function randomly
 *determines the parametes to use when calling playCard().
 *Params: state - a struct gameState.
 *Return: None.
 */
void playACard(struct gameState *state) {
   struct gameState testState;
   int handPosToPlay;
   int choice1, choice2, choice3;
   int playCardReturn = -1;
   char cardName[20];

   //Copy the gamestate so that we can test for a valid move before
   //performing that move on our actual gamestate.
   memcpy(&testState, state, sizeof(struct gameState));

   //Continue to try to play a card until a valid play is made.
   while (playCardReturn == -1) {


      //Randomly choose which card in the player's hand to play.
      handPosToPlay = rand() % numHandCards(&testState);
      //Randomly assign valid amounts to the choices for that card.
      choice1 = rand() % NUM_CARD_TYPES;
      choice2 = rand() % NUM_CARD_TYPES;
      choice3 = rand() % NUM_CARD_TYPES;

      //Try to play a card.
      playCardReturn = playCard(handPosToPlay, choice1, choice2, choice3, &testState);

      //If the the card wasn't played correctly. Reset the gamestate and try again.
      if (playCardReturn == -1) {
         memcpy(&testState, state, sizeof(struct gameState));
      }
   }
   //Play the card "for real"
   fprintf(stdout, "\t\tPlayer %d calls playCard() with parameters:\n", (state->whoseTurn + 1));
   fprintf(stdout, "\t\t\thandPos = %d\n", handPosToPlay);
   fprintf(stdout, "\t\t\tchoice1 = %d\n", choice1);
   fprintf(stdout, "\t\t\tchoice2 = %d\n", choice2);
   fprintf(stdout, "\t\t\tchoice3 = %d\n", choice3);
   cardNumToName((state->hand[whoseTurn(state)][handPosToPlay]), cardName);
   fprintf(stdout, "\t\tThe card played was a %s\n", cardName);
   playCard(handPosToPlay, choice1, choice2, choice3, state);
}


/*If player will buy a card on their turn, this function randomly
 *determines the parametes to use when calling buyCard().
 *Params: state - a struct gameState.
 *Return: None.
 */
void buyACard(struct gameState *state) {
   struct gameState testState;
   int cardToBuy;
   int buyCardReturn = -1;
   char cardName[20];
   int preDiscardCount;
   int preSupplyCount;

   //Copy the gamestate so that we can test for a valid move before
   //performing that move on our actual gamestate.
   memcpy(&testState, state, sizeof(struct gameState));

   //Continue to try to buy a card until a valid buy is made.
   while (buyCardReturn == -1) {

      //Randomly choose which type of card to buy.
      cardToBuy = rand() % NUM_CARD_TYPES;

      //Try to buy a card.
      buyCardReturn = buyCard(cardToBuy, &testState);

      //If the the card wasn't played correctly. Reset the gamestate and try again.
      if (buyCardReturn == -1) {
         memcpy(&testState, state, sizeof(struct gameState));
      }
   }
   //Buy the card "for real"
   preDiscardCount = state->discardCount[state->whoseTurn];
   preSupplyCount = state->supplyCount[cardToBuy];
   fprintf(stdout, "\t\tPlayer %d calls buyCard() with parameters:\n", (state->whoseTurn + 1));
   fprintf(stdout, "\t\t\tsupplyPos = %d\n", cardToBuy);
   cardNumToName(cardToBuy, cardName);
   fprintf(stdout, "\t\tThe card bought was a %s\n", cardName);
   buyCard(cardToBuy, state);

   //Check that buyCard() updated these variables correctly.
   assert( (state->discardCount[state->whoseTurn]) == (preDiscardCount + 1) );
   assert( (state->supplyCount[cardToBuy]) == (preSupplyCount - 1) );

}


/*Goes through the process of playing a complete game of dominion.  After
 *initializing the struct gameState, players randomly choose to play cards
 *and buy cards until the game is over.  Then the winner is determined.
 *Assertions are used to test whether the gameState is updated correctly
 *as the game proceeds.
 *Params: None.
 *Return: None.
 */
void playDominion() {

   int *kingCards;
   struct gameState state;
   int i, j;
   char cardName[20];
   int yesNo;
   int *whoWins = malloc(sizeof(int) * MAX_PLAYERS);
   int returnValue;
   int testWhoseTurn = 0;
   int playerAI[MAX_PLAYERS];

   //Set parameters to use in call to initializeGame().
   kingCards = setRandomGameInitializers();
   fprintf(stdout, "The kingdom cards used to initialize this game were:\n");
   for (i = 0; i < 10; i++) {
      cardNumToName(kingCards[i], cardName);
      fprintf(stdout, "\t%s\n", cardName);
   }
   fprintf(stdout, "The number of players in this game = %d\n", numPlayers);
   fprintf(stdout, "The seed number for this game = %d\n", numSeed);

   returnValue = initializeGame(numPlayers, kingCards, numSeed, &state);

   //Checking if initializeGame() performed correctly.
   assert(returnValue == 0);

   fprintf(stdout, "\nAfter game initialization, the counts of all card piles are:\n");
   for (i = 0; i < NUM_CARD_TYPES; i++) {
      cardNumToName(i, cardName);
      fprintf(stdout, "\t%s = %d\n", cardName, state.supplyCount[i]);
   }

   fprintf(stdout, "\nThe following is a record of the gameplay for this game:\n\n");

   //Assign player AIs.
   assignAIs(playerAI);


   //Play the game until it is over.
   while (!isGameOver(&state)) {
      //Checking if isGameOver() performed correctly.
      assert(isGameOver(&state) == testIsGameOver(&state));

      //Checking if whoseTurn() performed correctly.
      assert(state.whoseTurn == testWhoseTurn);

      fprintf(stdout, "\tPLAYER %d TURN\n", (state.whoseTurn + 1));

      //While the player is allowed to play a card.
      while (state.numActions > 0) {

         //Determine if player is going to play a card
         if (playerAI[state.whoseTurn] == 1 || playerAI[state.whoseTurn] == 3){
            yesNo = 1;
         }
         else if (playerAI[state.whoseTurn] == 2){
            yesNo = 0;
         }
         else {
            yesNo = rand() % 2;
         }

         if (yesNo == 1) {
            //We are going to play a card but we can only play a card if
            //the player has a card to play in their hand.
            for (j = 0; j < numHandCards(&state); j++){
               if ((handCard(j, &state) >= adventurer) && (handCard(j, &state) <= treasure_map)) {
                  playACard(&state);
                  break; //Only one card in the hand should be played.
               }
            }
         }
         else {
            //Player chooses to not play a card.
            break;
         }
      }

      //While the player is allowed to buy a card.
      while (state.numBuys > 0) {

         //Determine if player is going to buy a card
         if (playerAI[state.whoseTurn] == 2 || playerAI[state.whoseTurn] == 3){
            yesNo = 1;
         }
         else if (playerAI[state.whoseTurn] == 1){
            yesNo = 0;
         }
         else {
            yesNo = rand() % 2;
         }

         if (yesNo == 1) {
            //We are going to buy a card
            buyACard(&state);
         }
         else {
            //Player chooses to not buy a card.
            break;
         }
      }

      endTurn(&state);

      //Check that current player's hand has been discarded.
      assert(state.handCount[testWhoseTurn] == 0);

      //Check that next player's hand has been drawn.
      if ( (state.handCount[state.whoseTurn]
          + state.deckCount[state.whoseTurn]
          + state.discardCount[state.whoseTurn]) >= 5)
      {
         assert(state.handCount[state.whoseTurn] == 5);
      }


      //Check that current player's played cards have been cleared.
      //NOTE: Should additionally ask if player's new
      //discardCount == (oldDiscardCount + playedCardCount) to make sure
      //that cards have actually been moved to the right place.
      assert(state.playedCardCount == 0);

      //Update the variable used to test whoseTurn().
      if (testWhoseTurn == (numPlayers - 1)) {
         testWhoseTurn = 0;
      }
      else {
         testWhoseTurn++;
      }
   }

   //Checking if isGameOver() performed correctly.
   assert(isGameOver(&state) == testIsGameOver(&state));

   getWinners(whoWins, &state);

   //Determine who wins
   fprintf(stdout, "\nTHE WINNER(S) OF THE GAME ARE:\n");
   for (i = 0; i < MAX_PLAYERS; i++) {
      if (whoWins[i] == 1) {
         fprintf(stdout, "\tPlayer %d\n\n", (i+1));
      }
   }
}


int main (int argc, char** argv) {
   int i;

   srand(time(NULL)); //Set up the RNG that I use (rand)
   PlantSeeds(-1); //Set up the RNG that functions in dominion use (rngs.c)

   parseArgv(argc, argv);

   //Set the number of games to play.
   if (numGames < GAMES_MIN) {
      numGames = GAMES_MAX;
   }

   fprintf(stdout, "Total number of games played in this test = %d\n", numGames);
   fprintf(stderr, "Total number of games played in this test = %d\n", numGames);

   //Play the games.
   for (i = 0; i < numGames; i++) {
      fprintf(stdout, "\nRESULTS FROM GAME # %d\n\n", (i+1));
      fprintf(stderr, "\nRESULTS FROM GAME # %d\n\n", (i+1));
      playDominion();
      fprintf(stdout, "**************** END OF GAME %d ******************\n\n", (i+1));
   }

   return 0;
}
